﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace HTMLBoxLib
{
    /// <summary>
    /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
    ///
    /// Step 1a) Using this custom control in a XAML file that exists in the current project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:HTMLBox"
    ///
    ///
    /// Step 1b) Using this custom control in a XAML file that exists in a different project.
    /// Add this XmlNamespace attribute to the root element of the markup file where it is 
    /// to be used:
    ///
    ///     xmlns:MyNamespace="clr-namespace:HTMLBox;assembly=HTMLBox"
    ///
    /// You will also need to add a project reference from the project where the XAML file lives
    /// to this project and Rebuild to avoid compilation errors:
    ///
    ///     Right click on the target project in the Solution Explorer and
    ///     "Add Reference"->"Projects"->[Select this project]
    ///
    ///
    /// Step 2)
    /// Go ahead and use your control in the XAML file.
    ///
    ///     <MyNamespace:CustomControl1/>
    ///
    /// </summary>
    public class HTMLBox : TextBox
    {
        static HTMLBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(HTMLBox), new FrameworkPropertyMetadata(typeof(HTMLBox)));
        }

        private static int cursor_position = 0;
        private static string _lex = "";
        private static string _lex_value = "";

        private static byte _next_inline_R = 0;
        private static byte _next_inline_G = 0;
        private static byte _next_inline_B = 0;

        private static string[] _html_syntax_elements = new string[] {
                "<r="
            ,   "<g="
            ,   "<b="
        };
        private static string[] _delimiters = new string[] {
                ">"
            ,   ","
        };
        private static string _numerics = "0123456789";

         // <r=123><b=213><g=324>abc
        public static readonly DependencyProperty WholeTextProperty =
            DependencyProperty.Register("WholeText", typeof(string), typeof(HTMLBox),
            new FrameworkPropertyMetadata("",
                  FrameworkPropertyMetadataOptions.AffectsRender |
                  FrameworkPropertyMetadataOptions.AffectsParentMeasure
                  , new PropertyChangedCallback(OnWholeTextChanged)));

        private static void OnWholeTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var box = d as HTMLBox;
            var textblock = box.GetTemplateChild("RenderedText") as TextBlock;
            // added symbols
            if (cursor_position < box.CaretIndex)
                UpdateInlineCollection(textblock, box);
            // removed symbols
            else
            {
                // first remove last non-caret inline and update cursor pos
                // repeat while cursor pos > CaretIndex
                while (cursor_position >= box.CaretIndex)
                {
                    var last_text_inline = textblock.Inlines.Skip(textblock.Inlines.Count - 2).FirstOrDefault();
                    while ((last_text_inline as Run).Text.Length == 0)
                    {
                        textblock.Inlines.Remove(last_text_inline);
                        last_text_inline = textblock.Inlines.Skip(textblock.Inlines.Count - 2).FirstOrDefault();
                    }
                    (last_text_inline as Run).Text = (last_text_inline as Run).Text.Substring(0, (last_text_inline as Run).Text.Length - 1);
                    cursor_position -= 1;
                }
                // now do as if cursor pos < CareIndex
                UpdateInlineCollection(textblock, box);
            }
        }

        private static void UpdateInlineCollection(TextBlock textblock, TextBox box)
        {
            // get symbols
            if (cursor_position < 0) cursor_position = 0;
            var symbols_added = box.Text.Substring(cursor_position, box.CaretIndex - cursor_position);
            if (symbols_added.Length == 1) // general input
            {
                // refresh caret pos
                cursor_position = box.CaretIndex;
                // check endline / whitespace
                // create new inline in case new line was created
                if (symbols_added == "\r\n" || symbols_added == " " || box.CaretIndex == 1)
                {
                    // reset color
                    _next_inline_R = 0;
                    _next_inline_G = 0;
                    _next_inline_B = 0;
                    // add new inline
                    if (textblock != null)
                    {
                        textblock.Inlines.InsertBefore(
                                                            textblock.Inlines.LastInline
                                                        , new Run(symbols_added)
                                                      );
                    }
                    else
                        textblock.Inlines.Add(new Run(box.Text));
                }
                // check HTML markup
                else if (symbols_added == "<" || (!_numerics.Contains(symbols_added) && _lex != "") && !_delimiters.Contains(symbols_added)) // lex starts
                {
                    _lex += symbols_added;
                }
                else if (_lex != "" && _numerics.Contains(symbols_added))
                {
                    _lex_value += symbols_added;
                }
                else if (symbols_added == ",") // enumeration
                {
                    ProcessLex(_lex, _lex_value);
                }
                else if (symbols_added == ">") // end of statement
                {
                    ProcessLex(_lex, _lex_value);
                    var last_text_inline = textblock.Inlines.Skip(textblock.Inlines.Count - 2).FirstOrDefault();
                    last_text_inline.Foreground = new SolidColorBrush(Color.FromRgb(
                                                                                          _next_inline_R
                                                                                        , _next_inline_G
                                                                                        , _next_inline_B
                                                                     ));

                }
                else
                {
                    var last_text_inline = textblock.Inlines.Skip(textblock.Inlines.Count - 2).FirstOrDefault();
                    (last_text_inline as Run).Text = (last_text_inline as Run).Text + symbols_added;
                }
            }
            else // TODO: replace every delimiter with space
            {
                // split string
                int current_index = 0;
                int lex_index = 0;
                string current_inline = "";
                do
                {
                    current_inline = NextInline(symbols_added, current_index);
                    current_index += current_inline.Length;
                    if (current_inline == "") break;
                    else
                    {
                        _lex = NextLex(current_inline, lex_index);
                        lex_index += _lex.Length;
                        _lex_value = NextLexValue(current_inline, lex_index);
                        ProcessLex(_lex, _lex_value);
                        var last_text_inline = textblock.Inlines.Skip(textblock.Inlines.Count - 2).FirstOrDefault();
                        last_text_inline.Foreground = new SolidColorBrush(Color.FromRgb(
                                                                                              _next_inline_R
                                                                                            , _next_inline_G
                                                                                            , _next_inline_B
                                                                         ));
                        (last_text_inline as Run).Text = (last_text_inline as Run).Text + current_inline.Substring(lex_index, current_inline.Length - lex_index);
                    }
                }
                while (current_inline != "");
            }
        }

        private static string NextInline(string source, int start)
        {
            int i = start;
            string result = "";
            while (source[i] != ' ' && source[i] != '\n' && source[i] != '\r')
                result += source[i++];
            return result;
        }

        private static string NextLex(string inline, int start)
        {
            int i = start;
            string result = "";
            while (!_html_syntax_elements.Contains(result) && i < inline.Length)
                result += inline[i++];
            return _html_syntax_elements.Contains(result) ? result : "";
        }

        private static string NextLexValue(string inline, int start)
        {
            int i = start;
            string result = "";
            while (_numerics.Contains(inline[i]))
                result += inline[i++];
            return result;
        }

        private static void ProcessLex(string lex, string lex_value)
        {
            switch (_lex)
            {
                case "<r=":
                    _next_inline_R = byte.Parse(_lex_value);
                    _lex_value = "";
                    break;
                case "<g=":
                    _next_inline_G = byte.Parse(_lex_value);
                    _lex_value = "";
                    break;
                case "<b=":
                    _next_inline_B = byte.Parse(_lex_value);
                    _lex_value = "";
                    break;
            }
            _lex = "";
        }

        private static FrameworkElement GetElementFromItemTemplate(FrameworkElement container,
                                                             ItemContainerGenerator containerGenerator,
                                                             Object Content,
                                                             string ElementName)
        {
            var item = (DependencyObject)(containerGenerator.ContainerFromItem(Content));
            ContentPresenter itemPresenter = FindVisualChild<ContentPresenter>(container);

            DataTemplate itemDataTemplate = itemPresenter.ContentTemplate;
            return (FrameworkElement)itemDataTemplate.FindName(ElementName, itemPresenter);
        }

        private static childItem FindVisualChild<childItem>(DependencyObject obj)
                where childItem : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is childItem)
                    return (childItem)child;
                else
                {
                    childItem childOfChild = FindVisualChild<childItem>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets or sets the HTML text
        /// </summary>
        /// <value>HTML text</value>
        public string WholeText
        {
            get { return (string)GetValue(WholeTextProperty); }
            set { SetValue(WholeTextProperty, value); }
        }


    }
}
